﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Polar.AI.Common;

namespace Polar.AI.Analysis
{
    /// <summary>
    /// Indicates the threat orientation type relative to source body.
    /// </summary>
    public enum EnumCollisionOrientation { NotMoving, HeadOn, PerpendicularMajor, PerpendicularMinor, Overtaking, DroppingAway }


    /// <summary>
    /// Given two bodies this class will provide an analysis of the two and provide situational calculations
    /// </summary>
    public class ThreatAnalysis
    {
        #region Constants...
        private const int DEFAULT_SIMULATION_ROUNDS = 3;
        #endregion

        #region Private Members...
        private int _simulationRounds = DEFAULT_SIMULATION_ROUNDS;
        private volatile float _separationDistance = -1;
        private object _threadLock = new object();
        #endregion

        #region Public Properties...
        public IBody Source { get; private set; }
        public IBody Target { get; private set; }
        public EnumCollisionOrientation CollisionOrientation { get; private set; }
        public float OrientationAngle { get; private set; }
        public Vector2 SeparationVector { get; private set; }
        public float SeparationDistance
        {
            get
            {
                lock (_threadLock)
                {
                    if (_separationDistance < 1.0f)
                        _separationDistance = SeparationVector.Length();
                }

                return _separationDistance;
            }
        }
        public float SourceAngleToTarget { get; private set; }
        public CollisionAnalysis CollisionVector { get; private set; }

        public bool IsSourceStatic { get { return Source.LinearVelocity.IsZero(); } }
        public bool IsTargetStatic { get { return Target.LinearVelocity.IsZero(); } }
        #endregion


        #region Constructor...
        public ThreatAnalysis(IBody source, IBody target)
        {
            Init(source, target, DEFAULT_SIMULATION_ROUNDS);
        }

        public ThreatAnalysis(IBody source, IBody target, int simulationRounds)
        {
            Init(source, target, simulationRounds);
        }

        private void Init(IBody source, IBody target, int simulationRounds)
        {
            Source = source;
            Target = target;
            _simulationRounds = simulationRounds;

            if (IsSourceStatic == false)
                RunAnalysisPipeline();
            else
                CollisionVector = new CollisionAnalysis();
        }

        #endregion

        #region Private Methods...
        private void RunAnalysisPipeline()
        {
            Vector2 sForward = Source.Forward();
            Vector2 tForward = Target.Forward();

            OrientationAngle = Vector2.Dot(sForward, tForward);
            CollisionOrientation = DetermineCollisionOrientation(Source, Target, OrientationAngle);

            SeparationVector = Target.Position - Source.Position;

            SourceAngleToTarget = CalculateSourceAngleToTarget(sForward, SeparationVector);

            CollisionVector = SimulateCollision();
        }

        private float CalculateSourceAngleToTarget(Vector2 sourceForward, Vector2 separationVector)
        {
            if (separationVector.IsZero())
                return 0;
            else
                return Calc.VectorAngleRadians(sourceForward, Vector2.Normalize(separationVector));
        }

        private EnumCollisionOrientation DetermineCollisionOrientation(IBody source, IBody target, float orientationAngle)
        {
            if (source.LinearVelocity.IsZero()) return EnumCollisionOrientation.NotMoving;

            if (orientationAngle < -Calc.Cos45) //anti-parralel (head-on): steer away from future collision
            {
                return EnumCollisionOrientation.HeadOn;
            }
            else
            {
                if (orientationAngle > Calc.Cos45) //parallel: are we approaching them or dropping away
                {
                    if (target.LinearVelocity.LengthSquared() <= source.LinearVelocity.LengthSquared())
                        return EnumCollisionOrientation.Overtaking;
                    else
                        return EnumCollisionOrientation.DroppingAway;
                }
                else //perpendicular: are we faster or slower
                {
                    if (target.LinearVelocity.LengthSquared() <= source.LinearVelocity.LengthSquared())
                        return EnumCollisionOrientation.PerpendicularMajor;
                    else
                        return EnumCollisionOrientation.PerpendicularMinor;
                }
            }
        }

        private CollisionAnalysis SimulateCollision()
        {
            switch (CollisionOrientation)
            {
                case EnumCollisionOrientation.HeadOn:
                    return CollisionAnalysis.SimulateCollision(this, _simulationRounds);
                case EnumCollisionOrientation.Overtaking:
                    return CollisionAnalysis.SimulateCollision(this, _simulationRounds);
                case EnumCollisionOrientation.PerpendicularMajor:
                case EnumCollisionOrientation.PerpendicularMinor:
                    return CollisionAnalysis.SimulateCollision(this, _simulationRounds);
                case EnumCollisionOrientation.DroppingAway:
                case EnumCollisionOrientation.NotMoving:
                    return new CollisionAnalysis();
                default:
                    throw new NotSupportedException(string.Format("The following enum is not supported in SimulateCollision: {0}", CollisionOrientation));
            }
        }
        #endregion
    }
}

